Skip to content

Commit 257eb75

Browse files
dchakrav-githubdiwakar
andauthored
- Fixing CallChain to be easier to use for consistent pattern to use (#242)
- Adding DelayFactory that allows for plugguable model to override backoff strategies Co-authored-by: diwakar <diwakar@amazon.com>
1 parent ddfbcbf commit 257eb75

20 files changed

+738
-87
lines changed

src/main/java/software/amazon/cloudformation/LambdaWrapper.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import software.amazon.cloudformation.proxy.CallbackAdapter;
6363
import software.amazon.cloudformation.proxy.CloudFormationCallbackAdapter;
6464
import software.amazon.cloudformation.proxy.Credentials;
65+
import software.amazon.cloudformation.proxy.DelayFactory;
6566
import software.amazon.cloudformation.proxy.HandlerErrorCode;
6667
import software.amazon.cloudformation.proxy.HandlerRequest;
6768
import software.amazon.cloudformation.proxy.LoggerProxy;
@@ -126,7 +127,7 @@ protected LambdaWrapper() {
126127
this.typeReference = getTypeReference();
127128
}
128129

129-
/**
130+
/*
130131
* This .ctor provided for testing
131132
*/
132133
public LambdaWrapper(final CallbackAdapter<ResourceT> callbackAdapter,
@@ -385,7 +386,8 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp
385386
if (request.getRequestData().getCallerCredentials() != null) {
386387
awsClientProxy = new AmazonWebServicesClientProxy(requestContext == null, this.loggerProxy,
387388
request.getRequestData().getCallerCredentials(),
388-
() -> (long) context.getRemainingTimeInMillis());
389+
() -> (long) context.getRemainingTimeInMillis(),
390+
DelayFactory.CONSTANT_DEFAULT_DELAY_FACTORY);
389391
}
390392

391393
boolean computeLocally = true;
@@ -600,14 +602,24 @@ protected abstract ResourceHandlerRequest<ResourceT> transform(HandlerRequest<Re
600602

601603
/**
602604
* Implemented by the handler package as the key entry point.
605+
*
606+
* @param proxy Amazon webservice proxy to inject credentials correctly.
607+
* @param request incoming request for the call
608+
* @param action which action to take {@link Action#CREATE},
609+
* {@link Action#DELETE}, {@link Action#READ} {@link Action#LIST} or
610+
* {@link Action#UPDATE}
611+
* @param callbackContext the callback context to handle reentrant calls
612+
* @return progress event indicating success, in progress with delay callback or
613+
* failed state
614+
* @throws Exception propagate any unexpected errors
603615
*/
604616
public abstract ProgressEvent<ResourceT, CallbackT> invokeHandler(AmazonWebServicesClientProxy proxy,
605617
ResourceHandlerRequest<ResourceT> request,
606618
Action action,
607619
CallbackT callbackContext)
608620
throws Exception;
609621

610-
/**
622+
/*
611623
* null-safe exception metrics delivery
612624
*/
613625
private void publishExceptionMetric(final Action action, final Throwable ex, final HandlerErrorCode handlerErrorCode) {

src/main/java/software/amazon/cloudformation/injection/CredentialsProvider.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
public interface CredentialsProvider {
2121

2222
/**
23-
* Return the current set of credentials for initialising AWS SDK Clients
23+
* @return the current set of credentials for initialising AWS SDK Clients
2424
*/
2525
AwsSessionCredentials get();
2626

2727
/**
2828
* Inject a new set of credentials (passed through from caller)
29+
*
30+
* @param credentials, incoming credentials for the call that is being made
2931
*/
3032
void setCredentials(Credentials credentials);
3133
}

src/main/java/software/amazon/cloudformation/proxy/AmazonWebServicesClientProxy.java

Lines changed: 136 additions & 28 deletions
Large diffs are not rendered by default.

src/main/java/software/amazon/cloudformation/proxy/CallChain.java

Lines changed: 163 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*
3232
* <ol>
3333
* <li>{@link CallChain#initiate(String, ProxyClient, Object, StdCallbackContext)}
34-
* <li>{@link RequestMaker#request(Function)}
34+
* <li>{@link RequestMaker#translate(Function)}
3535
* <li>{@link Caller#call(BiFunction)}
3636
* <li>{@link Completed#done(Function)}
3737
* </ol>
@@ -40,6 +40,79 @@
4040
*/
4141
public interface CallChain {
4242

43+
/**
44+
* Provides an API initiator interface that works for all API calls that need
45+
* conversion, retry-backoff strategy, common exception handling and more
46+
* against desired state of the resource and callback context. This needs to be
47+
* instantiated once with the client, model and callback context and used. It
48+
* takes a reference to the desired state model, so any changes made on the
49+
* model object will be reflected here. The model and callback can accessed from
50+
* the instantiator instance
51+
*
52+
* @param <ClientT> the AWS Service client.
53+
* @param <ModelT> the model object being worked on
54+
* @param <CallbackT> the callback context
55+
*/
56+
interface Initiator<ClientT, ModelT, CallbackT extends StdCallbackContext> {
57+
/**
58+
* Each service call must be first initiated. Every call is provided a separate
59+
* name called call graph. This is essential from both a tracing perspective as
60+
* well as {@link StdCallbackContext} automated replay capabilities.
61+
*
62+
* @param callGraph, the name of the service operation this call graph is about.
63+
* @return Provides the next logical set in the fluent API.
64+
*/
65+
RequestMaker<ClientT, ModelT, CallbackT> initiate(String callGraph);
66+
67+
/**
68+
* @return the model associated with the API initiator. Can not be null
69+
*/
70+
ModelT getResourceModel();
71+
72+
/**
73+
* @return the callback context associated with API initiator, Can not be null
74+
*/
75+
CallbackT getCallbackContext();
76+
77+
/**
78+
* Can rebind a new model to the call chain while retaining the client and
79+
* callback context
80+
*
81+
* @param model, the new model for the callchain initiation
82+
* @param <NewModelT>, this actual model type
83+
* @return new {@link Initiator} that now has the new model associated with it
84+
*/
85+
<NewModelT> Initiator<ClientT, NewModelT, CallbackT> rebindModel(NewModelT model);
86+
87+
/**
88+
* Can rebind a new callback context for a call chain while retaining the model
89+
* and client
90+
*
91+
* @param callback the new callback context
92+
* @param <NewCallbackT> new callback context type
93+
* @return new {@link Initiator} that now has the new callback associated with
94+
* it
95+
*/
96+
<NewCallbackT extends StdCallbackContext> Initiator<ClientT, ModelT, NewCallbackT> rebindCallback(NewCallbackT callback);
97+
}
98+
99+
/**
100+
* factory method can created an {@link Initiator}
101+
*
102+
* @param client AWS Service Client. Recommend using Sync client as the
103+
* framework handles interleaving as needed.
104+
* @param model the resource desired state model, usually
105+
* @param context callback context that tracks all outbound API calls
106+
* @param <ClientT> Actual client e.g. KinesisClient.
107+
* @param <ModelT> The type (POJO) of Resource model.
108+
* @param <CallbackT>, callback context the extends {@link StdCallbackContext}
109+
*
110+
* @return an instance of the {@link Initiator}
111+
*/
112+
<ClientT, ModelT, CallbackT extends StdCallbackContext>
113+
Initiator<ClientT, ModelT, CallbackT>
114+
newInitiator(ProxyClient<ClientT> client, ModelT model, CallbackT context);
115+
43116
/**
44117
* Each service call must be first initiated. Every call is provided a separate
45118
* name called call graph. This is eseential from both a tracing perspective as
@@ -71,32 +144,54 @@ public interface CallChain {
71144
*/
72145
interface RequestMaker<ClientT, ModelT, CallbackT extends StdCallbackContext> {
73146
/**
147+
* use {@link #translate(Function)}
148+
*
74149
* Take a reference to the tranlater that take the resource model POJO as input
75150
* and provide a request object as needed to make the Service call.
76151
*
77152
* @param maker, provide a functional transform from model to request object.
78153
* @param <RequestT>, the web service request created
79154
* @return returns the next step, to actually call the service.
80155
*/
81-
<RequestT> Caller<RequestT, ClientT, ModelT, CallbackT> request(Function<ModelT, RequestT> maker);
156+
@Deprecated
157+
default <RequestT> Caller<RequestT, ClientT, ModelT, CallbackT> request(Function<ModelT, RequestT> maker) {
158+
return translate(maker);
159+
}
160+
161+
/**
162+
* Take a reference to the tranlater that take the resource model POJO as input
163+
* and provide a request object as needed to make the Service call.
164+
*
165+
* @param maker, provide a functional transform from model to request object.
166+
* @param <RequestT>, the web service request created
167+
* @return returns the next step, to actually call the service.
168+
*/
169+
<RequestT> Caller<RequestT, ClientT, ModelT, CallbackT> translate(Function<ModelT, RequestT> maker);
82170
}
83171

84172
/**
85173
* This Encapsulates the actual Call to the service that is being made via
86174
* caller. This allow for the proxy to intercept and wrap the caller in cases of
87175
* replay and provide the memoized response back
88176
*
89-
* @param <RequestT>
90-
* @param <ClientT>
91-
* @param <ModelT>
92-
* @param <CallbackT>
177+
* @param <RequestT>, the AWS serivce request we are making
178+
* @param <ClientT>, the web service client to make the call
179+
* @param <ModelT>, the current model we are using
180+
* @param <CallbackT>, the callback context for handling all AWS service request
181+
* responses
93182
*/
94183
interface Caller<RequestT, ClientT, ModelT, CallbackT extends StdCallbackContext> {
95184
<ResponseT>
96185
Stabilizer<RequestT, ResponseT, ClientT, ModelT, CallbackT>
97186
call(BiFunction<RequestT, ProxyClient<ClientT>, ResponseT> caller);
98187

99-
Caller<RequestT, ClientT, ModelT, CallbackT> retry(Delay delay);
188+
@Deprecated
189+
default Caller<RequestT, ClientT, ModelT, CallbackT> retry(Delay delay) {
190+
return backoff(delay);
191+
}
192+
193+
Caller<RequestT, ClientT, ModelT, CallbackT> backoff(Delay delay);
194+
100195
}
101196

102197
/**
@@ -154,8 +249,39 @@ interface Exceptional<RequestT, ResponseT, ClientT, ModelT, CallbackT extends St
154249
* @return true of you want to attempt another retry of the operation. false to
155250
* indicate propagate error/fault.
156251
*/
252+
@Deprecated
253+
default Completed<RequestT, ResponseT, ClientT, ModelT, CallbackT>
254+
exceptFilter(Callback<? super RequestT, Exception, ClientT, ModelT, CallbackT, Boolean> handler) {
255+
return retryErrorFilter(handler);
256+
}
257+
258+
/**
259+
* @param handler, a predicate lambda expression that takes the web request,
260+
* exception, client, model and context to determine to retry the
261+
* exception thrown by the service or fail operation. This is the
262+
* simpler model then {@link #handleError(ExceptionPropagate)} for
263+
* most common retry scenarios If we need more control over the
264+
* outcome, then use {@link #handleError(ExceptionPropagate)}
265+
* @return true of you want to attempt another retry of the operation. false to
266+
* indicate propagate error/fault.
267+
*/
157268
Completed<RequestT, ResponseT, ClientT, ModelT, CallbackT>
158-
exceptFilter(Callback<? super RequestT, Exception, ClientT, ModelT, CallbackT, Boolean> handler);
269+
retryErrorFilter(Callback<? super RequestT, Exception, ClientT, ModelT, CallbackT, Boolean> handler);
270+
271+
/**
272+
* @param handler, a lambda expression that takes the web request, response,
273+
* client, model and context returns a successful or failed
274+
* {@link ProgressEvent} back or can rethrow service exception to
275+
* propagate errors. If handler needs to retry the exception, the it
276+
* will throw a
277+
* {@link software.amazon.awssdk.core.exception.RetryableException}
278+
* @return a ProgressEvent for the model
279+
*/
280+
@Deprecated
281+
default Completed<RequestT, ResponseT, ClientT, ModelT, CallbackT> exceptHandler(ExceptionPropagate<? super RequestT,
282+
Exception, ClientT, ModelT, CallbackT, ProgressEvent<ModelT, CallbackT>> handler) {
283+
return handleError(handler);
284+
}
159285

160286
/**
161287
* @param handler, a lambda expression that take the web request, response,
@@ -164,8 +290,26 @@ interface Exceptional<RequestT, ResponseT, ClientT, ModelT, CallbackT extends St
164290
* @return If status is {@link OperationStatus#IN_PROGRESS} we will attempt
165291
* another retry. Otherwise failure is propagated.
166292
*/
167-
Completed<RequestT, ResponseT, ClientT, ModelT, CallbackT> exceptHandler(Callback<? super RequestT, Exception, ClientT,
168-
ModelT, CallbackT, ProgressEvent<ModelT, CallbackT>> handler);
293+
Completed<RequestT, ResponseT, ClientT, ModelT, CallbackT> handleError(ExceptionPropagate<? super RequestT, Exception,
294+
ClientT, ModelT, CallbackT, ProgressEvent<ModelT, CallbackT>> handler);
295+
}
296+
297+
/**
298+
* When implementing this interface, developers can either propagate the
299+
* exception as is. If the exception can be retried, throw
300+
* {@link software.amazon.awssdk.core.exception.RetryableException}
301+
*
302+
* @param <RequestT> the API request object
303+
* @param <E> the exception that is thrown by the API
304+
* @param <ClientT> the service client
305+
* @param <ModelT> current desired state resource model
306+
* @param <CallbackT> current callback context
307+
* @param <ReturnT> result object
308+
*/
309+
@FunctionalInterface
310+
interface ExceptionPropagate<RequestT, E extends Exception, ClientT, ModelT, CallbackT extends StdCallbackContext, ReturnT> {
311+
ReturnT invoke(RequestT request, E exception, ProxyClient<ClientT> client, ModelT model, CallbackT context)
312+
throws Exception;
169313
}
170314

171315
/**
@@ -242,24 +386,27 @@ interface Completed<RequestT, ResponseT, ClientT, ModelT, CallbackT extends StdC
242386
done(Callback<RequestT, ResponseT, ClientT, ModelT, CallbackT, ProgressEvent<ModelT, CallbackT>> callback);
243387

244388
/**
245-
* Helper function that provides a {@link OperationStatus#SUCCESS} status when
246-
* the callchain is done
389+
* @return {@link ProgressEvent} Helper function that provides a
390+
* {@link OperationStatus#SUCCESS} status when the callchain is done
247391
*/
248392
default ProgressEvent<ModelT, CallbackT> success() {
249393
return done((request, response, client, model, context) -> ProgressEvent.success(model, context));
250394
}
251395

252396
/**
253-
* Helper function that provides a {@link OperationStatus#IN_PROGRESS} status
254-
* when the callchain is done
397+
* @return {@link ProgressEvent} Helper function that provides a
398+
* {@link OperationStatus#IN_PROGRESS} status when the callchain is done
255399
*/
256400
default ProgressEvent<ModelT, CallbackT> progress() {
257401
return progress(0);
258402
}
259403

260404
/**
261-
* Helper function that provides a {@link OperationStatus#IN_PROGRESS} status
262-
* when the callchain is done
405+
* @param callbackDelay the number of seconds to delay before calling back into
406+
* this externally
407+
* @return {@link ProgressEvent} Helper function that provides a
408+
* {@link OperationStatus#IN_PROGRESS} status when the callchain is done
409+
* with callback delay
263410
*/
264411
default ProgressEvent<ModelT, CallbackT> progress(int callbackDelay) {
265412
return done((request, response, client, model, context) -> ProgressEvent.defaultInProgressHandler(context,

src/main/java/software/amazon/cloudformation/proxy/CallbackAdapter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public interface CallbackAdapter<T> {
3333
*
3434
* @param bearerToken unique identifier for this provisioning operation
3535
* @param errorCode (optional) error code in case of fault
36-
* @param operationStatus current status of provisioning operation
36+
* @param operationStatus new status of provisioning operation
37+
* @param currentOperationStatus current status of provisioning operation
3738
* @param resourceModel the current state of the provisioned resource
3839
* @param statusMessage (optional) progress status which may be shown to end
3940
* user

src/main/java/software/amazon/cloudformation/proxy/Delay.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,8 @@ public interface Delay {
4747
* values
4848
*
4949
* @param attempt, starts with 1
50-
* @return the next amount to stabilize for. return -1 to indicate delay is
51-
* complete
52-
* @return the next amount to stabilize for. returns {@link Duration#ZERO} to
53-
* indicate delay is complete
50+
* @return the next amount to wait for or {@link Duration#ZERO} to indicate
51+
* delay is complete
5452
*/
5553
Duration nextDelay(int attempt);
5654

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package software.amazon.cloudformation.proxy;
16+
17+
import java.time.Duration;
18+
import software.amazon.cloudformation.proxy.delay.Constant;
19+
20+
@FunctionalInterface
21+
public interface DelayFactory {
22+
DelayFactory CONSTANT_DEFAULT_DELAY_FACTORY = (apiCall, incoming) -> incoming != null
23+
? incoming
24+
: Constant.of().delay(Duration.ofSeconds(5)).timeout(Duration.ofMinutes(20)).build();
25+
26+
Delay getDelay(String apiCall, Delay provided);
27+
28+
}

0 commit comments

Comments
 (0)