Skip to content
This repository was archived by the owner on Oct 23, 2025. It is now read-only.

Commit 73512c1

Browse files
authored
Fixes for workgroup stabilization before create and delete operations (#63)
* Update Tags Support for Namespace * Workgroup stabilization fixes and source to target operation failure fixes * Changes to accommodate for Uluru Tags contract tests
1 parent e62cfd9 commit 73512c1

File tree

16 files changed

+167
-66
lines changed

16 files changed

+167
-66
lines changed

aws-redshiftserverless-workgroup/.rpdk-config

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,8 @@
2424
"codegen_template_path": "guided_aws",
2525
"protocolVersion": "2.0.0"
2626
},
27-
"executableEntrypoint": "software.amazon.redshiftserverless.workgroup.HandlerWrapperExecutable"
27+
"logProcessorEnabled": "true",
28+
"executableEntrypoint": "software.amazon.redshiftserverless.workgroup.HandlerWrapperExecutable",
29+
"contractSettings": {},
30+
"canarySettings": {}
2831
}

aws-redshiftserverless-workgroup/aws-redshiftserverless-workgroup.json

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,16 @@
267267
}
268268
},
269269
"tagging": {
270-
"taggable": true
270+
"taggable": true,
271+
"tagOnCreate": true,
272+
"tagUpdatable": true,
273+
"cloudFormationSystemTags": false,
274+
"tagProperty": "/properties/Tags",
275+
"permissions": [
276+
"redshift-serverless:ListTagsForResource",
277+
"redshift-serverless:TagResource",
278+
"redshift-serverless:UntagResource"
279+
]
271280
},
272281
"additionalProperties": false,
273282
"required": [
@@ -307,10 +316,7 @@
307316
"/properties/MaxCapacity",
308317
"/properties/ConfigParameters",
309318
"/properties/SecurityGroupIds",
310-
"/properties/SubnetIds",
311-
"/properties/Tags",
312-
"/properties/Tags/*/Key",
313-
"/properties/Tags/*/Value"
319+
"/properties/SubnetIds"
314320
],
315321
"primaryIdentifier": [
316322
"/properties/WorkgroupName"
@@ -328,7 +334,9 @@
328334
"redshift-serverless:CreateNamespace",
329335
"redshift-serverless:CreateWorkgroup",
330336
"redshift-serverless:GetWorkgroup",
331-
"redshift-serverless:GetNamespace"
337+
"redshift-serverless:GetNamespace",
338+
"redshift-serverless:ListTagsForResource",
339+
"redshift-serverless:TagResource"
332340
]
333341
},
334342
"read": {
@@ -340,7 +348,8 @@
340348
"ec2:DescribeSubnets",
341349
"ec2:DescribeAccountAttributes",
342350
"ec2:DescribeAvailabilityZones",
343-
"redshift-serverless:GetWorkgroup"
351+
"redshift-serverless:GetWorkgroup",
352+
"redshift-serverless:ListTagsForResource"
344353
]
345354
},
346355
"update": {
@@ -356,7 +365,10 @@
356365
"redshift-serverless:TagResource",
357366
"redshift-serverless:UntagResource",
358367
"redshift-serverless:GetWorkgroup",
359-
"redshift-serverless:UpdateWorkgroup"
368+
"redshift-serverless:UpdateWorkgroup",
369+
"redshift-serverless:ListTagsForResource",
370+
"redshift-serverless:TagResource",
371+
"redshift-serverless:UntagResource"
360372
]
361373
},
362374
"delete": {
@@ -370,7 +382,9 @@
370382
"ec2:DescribeAvailabilityZones",
371383
"redshift-serverless:GetWorkgroup",
372384
"redshift-serverless:GetNamespace",
373-
"redshift-serverless:DeleteWorkgroup"
385+
"redshift-serverless:DeleteWorkgroup",
386+
"redshift-serverless:ListTagsForResource",
387+
"redshift-serverless:UntagResource"
374388
]
375389
},
376390
"list": {
@@ -382,7 +396,8 @@
382396
"ec2:DescribeSubnets",
383397
"ec2:DescribeAccountAttributes",
384398
"ec2:DescribeAvailabilityZones",
385-
"redshift-serverless:ListWorkgroups"
399+
"redshift-serverless:ListWorkgroups",
400+
"redshift-serverless:ListTagsForResource"
386401
]
387402
}
388403
}

aws-redshiftserverless-workgroup/docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,4 @@ Returns the <code>PubliclyAccessible</code> value.
276276
#### CreationDate
277277

278278
Returns the <code>CreationDate</code> value.
279+

aws-redshiftserverless-workgroup/docs/configparameter.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ _Type_: String
4141
_Maximum Length_: <code>15000</code>
4242

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

aws-redshiftserverless-workgroup/docs/tag.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ _Type_: String
4343
_Maximum Length_: <code>256</code>
4444

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

aws-redshiftserverless-workgroup/docs/workgroup.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ _Required_: No
3838
_Type_: <a href="endpoint.md">Endpoint</a>
3939

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

aws-redshiftserverless-workgroup/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
<artifactId>redshiftserverless</artifactId>
3333
<version>2.22.5</version>
3434
</dependency>
35+
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/secretsmanager -->
36+
<dependency>
37+
<groupId>software.amazon.awssdk</groupId>
38+
<artifactId>secretsmanager</artifactId>
39+
<version>2.22.5</version>
40+
</dependency>
3541
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
3642
<dependency>
3743
<groupId>org.projectlombok</groupId>

aws-redshiftserverless-workgroup/src/main/java/software/amazon/redshiftserverless/workgroup/BaseHandlerStd.java

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import software.amazon.awssdk.services.redshiftserverless.model.GetNamespaceResponse;
1111
import software.amazon.awssdk.services.redshiftserverless.model.GetWorkgroupRequest;
1212
import software.amazon.awssdk.services.redshiftserverless.model.GetWorkgroupResponse;
13+
import software.amazon.awssdk.services.redshiftserverless.model.ListTagsForResourceRequest;
14+
import software.amazon.awssdk.services.redshiftserverless.model.ListTagsForResourceResponse;
1315
import software.amazon.awssdk.services.redshiftserverless.model.Namespace;
1416
import software.amazon.awssdk.services.redshiftserverless.model.NamespaceStatus;
1517
import software.amazon.awssdk.services.redshiftserverless.model.WorkgroupStatus;
@@ -38,7 +40,7 @@ public abstract class BaseHandlerStd extends BaseHandler<CallbackContext> {
3840
protected Logger logger;
3941

4042
public static final String BUSY_WORKGROUP_RETRY_EXCEPTION_MESSAGE =
41-
"There is an operation running on the existing workgroup";
43+
"There is an operation in progress";
4244

4345
// This is for delete workgroup operation. We need AdminWF to finish the operation completely
4446
// This is needed for CTV2 to work
@@ -54,7 +56,7 @@ protected static boolean isRetriableWorkgroupException(ConflictException excepti
5456
.build();
5557

5658
protected static final Constant PREOPERATION_BACKOFF_STRATEGY = Constant.of()
57-
.timeout(Duration.ofMinutes(5L))
59+
.timeout(Duration.ofMinutes(60L))
5860
.delay(Duration.ofSeconds(5L))
5961
.build();
6062

@@ -130,14 +132,40 @@ protected UpdateWorkgroupResponse updateWorkgroup(final UpdateWorkgroupRequest a
130132

131133
}
132134

135+
// Since there are source to target operations on the cluster
136+
// We will receive operation in progress in such cases.
137+
// This needs to be handled on CFN side as this will break contract tests
133138
protected DeleteWorkgroupResponse deleteWorkgroup(final DeleteWorkgroupRequest awsRequest,
134-
final ProxyClient<RedshiftServerlessClient> proxyClient) {
135-
136-
DeleteWorkgroupResponse awsResponse = proxyClient.injectCredentialsAndInvokeV2(
137-
awsRequest, proxyClient.client()::deleteWorkgroup);
138-
139-
logger.log(String.format("Workgroup : %s has successfully been deleted.", awsResponse.workgroup().workgroupName()));
140-
logger.log(awsResponse.toString());
139+
final ProxyClient<RedshiftServerlessClient> proxyClient) throws ConflictException {
140+
boolean operationInProgress = false;
141+
int max_retries = 5;
142+
int current_retry = 0;
143+
DeleteWorkgroupResponse awsResponse = null;
144+
145+
do {
146+
try {
147+
awsResponse = proxyClient.injectCredentialsAndInvokeV2(awsRequest, proxyClient.client()::deleteWorkgroup);
148+
logger.log(String.format("Workgroup : %s has successfully been deleted.", awsResponse.workgroup().workgroupName()));
149+
logger.log(awsResponse.toString());
150+
operationInProgress = false;
151+
} catch (ConflictException e) {
152+
Pattern pattern = Pattern.compile(".*There is an operation in progress.*", Pattern.CASE_INSENSITIVE);
153+
if(pattern.matcher(e.getMessage()).matches()) {
154+
logger.log("There is an operation in progress during delete. We will wait and retry in 60 secs");
155+
operationInProgress = true;
156+
current_retry = current_retry + 1;
157+
// Since there are source to target operations on the cluster
158+
try {
159+
Thread.sleep(60000);
160+
} catch(InterruptedException ex) {
161+
Thread.currentThread().interrupt();
162+
}
163+
} else {
164+
// We need to explicitly catch operation in progress or reraise other conflict exceptions
165+
throw e;
166+
}
167+
}
168+
} while((current_retry < max_retries) && operationInProgress);
141169

142170
return awsResponse;
143171
}
@@ -205,6 +233,15 @@ protected boolean isWorkgroupDeleted(final Object awsRequest,
205233
return false;
206234
}
207235

236+
protected ListTagsForResourceResponse readTags(final ListTagsForResourceRequest awsRequest,
237+
final ProxyClient<RedshiftServerlessClient> proxyClient) {
238+
ListTagsForResourceResponse awsResponse;
239+
awsResponse = proxyClient.injectCredentialsAndInvokeV2(awsRequest, proxyClient.client()::listTagsForResource);
240+
241+
logger.log(String.format("%s's tags have successfully been read.", ResourceModel.TYPE_NAME));
242+
return awsResponse;
243+
}
244+
208245
protected ProgressEvent<ResourceModel, CallbackContext> defaultWorkgroupErrorHandler(final Object awsRequest,
209246
final Exception exception,
210247
final ProxyClient<RedshiftServerlessClient> client,

aws-redshiftserverless-workgroup/src/main/java/software/amazon/redshiftserverless/workgroup/CreateHandler.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
3535
this.logger = logger;
3636

3737
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
38+
.then(progress ->
39+
proxy.initiate("AWS-RedshiftServerless-Workgroup::Create::ReadNamespaceBeforeCreate", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
40+
.translateToServiceRequest(Translator::translateToReadNamespaceRequest)
41+
.backoffDelay(PREOPERATION_BACKOFF_STRATEGY)// We wait for max of 5mins here
42+
.makeServiceCall(this::readNamespace)
43+
.stabilize(this::isNamespaceStable) // This basically checks for namespace to be in stable state before we create workgroup
44+
.handleError(this::createWorkgroupErrorHandler)
45+
.done(awsResponse -> {
46+
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext);
47+
})
48+
)
3849
.then(progress ->
3950
proxy.initiate("AWS-RedshiftServerless-Workgroup::Create", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
4051
.translateToServiceRequest(Translator::translateToCreateRequest)
@@ -47,7 +58,7 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
4758
})
4859
)
4960
.then(progress ->
50-
proxy.initiate("AWS-RedshiftServerless-Workgroup::ReadNameSpace", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
61+
proxy.initiate("AWS-RedshiftServerless-Workgroup::ReadNameSpaceAfterCreate", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
5162
.translateToServiceRequest(Translator::translateToReadNamespaceRequest)
5263
.backoffDelay(BACKOFF_STRATEGY)
5364
.makeServiceCall(this::readNamespace)
@@ -67,6 +78,10 @@ private ProgressEvent<ResourceModel, CallbackContext> createWorkgroupErrorHandle
6778
logger.log(String.format("Operation: %s : encountered exception for model: %s",
6879
awsRequest.getClass().getName(), ResourceModel.TYPE_NAME));
6980
logger.log(awsRequest.toString());
81+
Pattern pattern = Pattern.compile(".*is not authorized to perform: redshift-serverless:TagResource.*", Pattern.CASE_INSENSITIVE);
82+
if(pattern.matcher(exception.getMessage()).matches()){
83+
return ProgressEvent.failed(model, context, HandlerErrorCode.UnauthorizedTaggingOperation, exception.getMessage());
84+
}
7085

7186
return this.defaultWorkgroupErrorHandler(awsRequest, exception, client, model, context);
7287
}

aws-redshiftserverless-workgroup/src/main/java/software/amazon/redshiftserverless/workgroup/DeleteHandler.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(final Amaz
3131
this.logger = logger;
3232

3333
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
34+
.then(progress ->
35+
proxy.initiate("AWS-RedshiftServerless-Workgroup::Delete::ReadWorkgroup", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
36+
.translateToServiceRequest(Translator::translateToReadRequest)
37+
.backoffDelay(PREOPERATION_BACKOFF_STRATEGY)// We wait for max of 5mins here
38+
.makeServiceCall(this::readWorkgroup)
39+
.stabilize(this::isWorkgroupStable) // This basically checks for workgroup to be in stable state before we delete workgroup
40+
.handleError(this::deleteWorkgroupErrorHandler)
41+
.done( awsResponse -> {
42+
return ProgressEvent.progress(Translator.translateFromReadResponse(awsResponse), callbackContext);
43+
})
44+
)
45+
.then(progress ->
46+
proxy.initiate("AWS-RedshiftServerless-Workgroup::Delete::ReadNamespaceBeforeDelete", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
47+
.translateToServiceRequest(Translator::translateToReadNamespaceRequest)
48+
.backoffDelay(PREOPERATION_BACKOFF_STRATEGY)// We wait for max of 5mins here
49+
.makeServiceCall(this::readNamespace)
50+
.stabilize(this::isNamespaceStable) // This basically checks for namespace to be in stable state before we delete workgroup
51+
.handleError(this::deleteWorkgroupErrorHandler)
52+
.done( awsResponse -> {
53+
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext);
54+
})
55+
)
3456
.then(progress ->
3557
proxy.initiate("AWS-RedshiftServerless-Workgroup::Delete", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
3658
.translateToServiceRequest(Translator::translateToDeleteRequest)
@@ -42,16 +64,6 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(final Amaz
4264
return ProgressEvent.progress(Translator.translateFromDeleteResponse(awsResponse), callbackContext);
4365
})
4466
)
45-
.then(progress -> {
46-
if (progress.getCallbackContext().isPropagationDelay()) {
47-
logger.log("Propagation delay completed");
48-
return ProgressEvent.progress(progress.getResourceModel(), progress.getCallbackContext());
49-
}
50-
progress.getCallbackContext().setPropagationDelay(true);
51-
logger.log("Setting propagation delay");
52-
return ProgressEvent.defaultInProgressHandler(progress.getCallbackContext(),
53-
EVENTUAL_CONSISTENCY_DELAY_SECONDS, progress.getResourceModel());
54-
})
5567
.then(progress ->
5668
proxy.initiate("AWS-RedshiftServerless-Workgroup::ReadNameSpaceAfterDelete", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
5769
.translateToServiceRequest(Translator::translateToReadNamespaceRequest)

0 commit comments

Comments
 (0)