Skip to content

Commit 7632341

Browse files
authored
Fix model in reschedule (#130)
* Add testing types * Add testing * Add testing input case * Update model on reinvoke * Remove extra line spacing * Remove unneeded logging
1 parent da8ed78 commit 7632341

File tree

4 files changed

+139
-21
lines changed

4 files changed

+139
-21
lines changed

cfn/cfn.go

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ func isMutatingAction(action string) bool {
199199
}
200200

201201
func translateStatus(operationStatus handler.Status) callback.Status {
202-
203202
switch operationStatus {
204203
case handler.Success:
205204
return callback.Success
@@ -214,42 +213,39 @@ func translateStatus(operationStatus handler.Status) callback.Status {
214213
}
215214

216215
func processinvoke(handlerFn handlerFunc, event *event, request handler.Request, metricsPublisher *metrics.Publisher) handler.ProgressEvent {
217-
218216
progEvt, err := invoke(handlerFn, request, metricsPublisher, event.Action)
219-
220217
if err != nil {
221218
log.Printf("Handler invocation failed: %v", err)
222219
return handler.NewFailedEvent(err)
223220
}
224221
return progEvt
225-
226222
}
227223

228224
func reschedule(ctx context.Context, invokeScheduler InvokeScheduler, progEvt handler.ProgressEvent, event *event) (bool, error) {
229225
cusCtx, delay := marshalCallback(&progEvt)
230226
ids, err := scheduler.GenerateCloudWatchIDS()
231-
232227
if err != nil {
233228
return false, err
234229
}
235230
// Add IDs to recall the function with Cloudwatch events
236231
event.RequestContext.CloudWatchEventsRuleName = ids.Handler
237232
event.RequestContext.CloudWatchEventsTargetID = ids.Target
238-
233+
// Update model properties
234+
m, err := encoding.Marshal(progEvt.ResourceModel)
235+
if err != nil {
236+
return false, err
237+
}
238+
event.RequestData.ResourceProperties = m
239239
// Rebuild the context
240240
event.RequestContext.CallbackContext = cusCtx
241-
242241
callbackRequest, err := json.Marshal(event)
243-
244242
if err != nil {
245243
return false, err
246244
}
247245
scheResult, err := invokeScheduler.Reschedule(ctx, delay, string(callbackRequest), ids)
248-
249246
if err != nil {
250247
return false, err
251248
}
252-
253249
return scheResult.ComputeLocal, nil
254250
}
255251

@@ -313,18 +309,12 @@ func makeEventFunc(h Handler) eventFunc {
313309

314310
progEvt := processinvoke(handlerFn, event, request, metricsPublisher)
315311

316-
cusCtx, delay := marshalCallback(&progEvt)
317-
318312
r, err := newResponse(&progEvt, event.BearerToken)
319313
if err != nil {
320314
log.Printf("Error creating response: %v", err)
321315
return re.report(event, "Response error", err, unmarshalingError)
322316
}
323317

324-
log.Printf("Handler returned OperationStatus: %v Message: %v CallbackContext: %v Delay: %v, ErrorCode: %v ",
325-
r.OperationStatus, progEvt.Message,
326-
cusCtx, delay, progEvt.HandlerErrorCode)
327-
328318
if !isMutatingAction(event.Action) && r.OperationStatus == handler.InProgress {
329319
return re.report(event, "Response error", errors.New("READ and LIST handlers must return synchronous"), invalidRequestError)
330320
}
@@ -357,23 +347,18 @@ func makeEventFunc(h Handler) eventFunc {
357347
// contract testing framework to invoke the resource's CRUDL handlers.
358348
func makeTestEventFunc(h Handler) testEventFunc {
359349
return func(ctx context.Context, event *testEvent) (handler.ProgressEvent, error) {
360-
361350
handlerFn, err := router(event.Action, h)
362-
363351
if err != nil {
364352
return handler.NewFailedEvent(err), err
365353
}
366-
367354
request := handler.NewRequest(
368355
event.Request.LogicalResourceIdentifier,
369356
event.CallbackContext,
370357
credentials.SessionFromCredentialsProvider(&event.Credentials),
371358
event.Request.PreviousResourceState,
372359
event.Request.DesiredResourceState,
373360
)
374-
375361
progEvt := handlerFn(request)
376-
377362
return progEvt, nil
378363
}
379364
}

cfn/cfn_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import (
1010
"testing"
1111
"time"
1212

13+
"github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/encoding"
1314
"github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler"
1415
"github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/scheduler"
1516
"github.com/aws/aws-lambda-go/lambdacontext"
17+
"github.com/aws/aws-sdk-go/aws"
1618
"github.com/aws/aws-sdk-go/aws/session"
1719
"github.com/aws/aws-sdk-go/service/cloudformation"
1820
)
@@ -319,3 +321,72 @@ func loadTestEvent(path string, evt *testEvent) *testEvent {
319321
}
320322
return evt
321323
}
324+
325+
func TestMakeEventFuncModel(t *testing.T) {
326+
start := time.Now()
327+
future := start.Add(time.Minute * 15)
328+
tc, cancel := context.WithDeadline(context.Background(), future)
329+
defer cancel()
330+
lc := lambdacontext.NewContext(tc, &lambdacontext.LambdaContext{})
331+
f1 := func(r handler.Request) handler.ProgressEvent {
332+
m := MockModel{}
333+
if len(r.CallbackContext) == 1 {
334+
if err := r.Unmarshal(&m); err != nil {
335+
return handler.NewFailedEvent(err)
336+
}
337+
return handler.ProgressEvent{
338+
OperationStatus: handler.Success,
339+
ResourceModel: &m,
340+
Message: "Success",
341+
}
342+
}
343+
if err := r.Unmarshal(&m); err != nil {
344+
return handler.NewFailedEvent(err)
345+
}
346+
m.Property2 = aws.String("change")
347+
return handler.ProgressEvent{
348+
OperationStatus: handler.InProgress,
349+
Message: "In Progress",
350+
CallbackDelaySeconds: 3,
351+
CallbackContext: map[string]interface{}{"foo": "bar"},
352+
ResourceModel: &m,
353+
}
354+
}
355+
type args struct {
356+
h Handler
357+
ctx context.Context
358+
event *event
359+
}
360+
tests := []struct {
361+
name string
362+
args args
363+
want MockModel
364+
}{
365+
{"Test CREATE async local with model change", args{&MockModelHandler{f1}, lc, loadEvent("request.create2.json", &event{})}, MockModel{
366+
Property1: aws.String("abc"),
367+
Property2: aws.String("change"),
368+
}},
369+
}
370+
for _, tt := range tests {
371+
t.Run(tt.name, func(t *testing.T) {
372+
f := makeEventFunc(tt.args.h)
373+
got, err := f(tt.args.ctx, tt.args.event)
374+
if err != nil {
375+
t.Errorf("TestMakeEventFuncModel() = %v", err)
376+
return
377+
}
378+
model, err := encoding.Stringify(got.ResourceModel)
379+
if err != nil {
380+
t.Errorf("TestMakeEventFuncModel() = %v", err)
381+
}
382+
wantrModel, err := encoding.Stringify(tt.want)
383+
if err != nil {
384+
t.Errorf("TestMakeEventFuncModel() = %v", err)
385+
}
386+
if wantrModel != model {
387+
t.Errorf("response = %v; want %v", model, wantrModel)
388+
}
389+
390+
})
391+
}
392+
}

cfn/test/data/request.create2.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"awsAccountId": "123456789012",
3+
"bearerToken": "123456",
4+
"region": "us-east-1",
5+
"action": "CREATE",
6+
"responseEndpoint": "cloudformation.us-west-2.amazonaws.com",
7+
"resourceType": "AWS::Test::TestModel",
8+
"resourceTypeVersion": "1.0",
9+
"requestContext": {},
10+
"requestData": {
11+
"callerCredentials": {
12+
"accessKeyId": "IASAYK835GAIFHAHEI23",
13+
"secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0",
14+
"sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg"
15+
},
16+
"platformCredentials": {
17+
"accessKeyId": "32IEHAHFIAG538KYASAI",
18+
"secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66",
19+
"sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal"
20+
},
21+
"logicalResourceId": "myBucket",
22+
"resourceProperties": {
23+
"property1": "abc"
24+
}
25+
},
26+
"stackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968"
27+
}

cfn/types_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,38 @@ type MockScheduler struct {
109109
func (m MockScheduler) Reschedule(lambdaCtx context.Context, secsFromNow int64, callbackRequest string, invocationIDS *scheduler.ScheduleIDS) (*scheduler.Result, error) {
110110
return m.Result, m.Err
111111
}
112+
113+
// MockModel mocks a resource model
114+
//
115+
// This implementation of the handlers is only used for testing.
116+
type MockModel struct {
117+
Property1 *string `json:"property1,omitempty"`
118+
Property2 *string `json:"property2,omitempty"`
119+
}
120+
121+
// MockModelHandler is a implementation of Handler
122+
//
123+
// This implementation of the handlers is only used for testing.
124+
type MockModelHandler struct {
125+
fn func(r handler.Request) handler.ProgressEvent
126+
}
127+
128+
func (m *MockModelHandler) Create(request handler.Request) handler.ProgressEvent {
129+
return m.fn(request)
130+
}
131+
132+
func (m *MockModelHandler) Read(request handler.Request) handler.ProgressEvent {
133+
return m.fn(request)
134+
}
135+
136+
func (m *MockModelHandler) Update(request handler.Request) handler.ProgressEvent {
137+
return m.fn(request)
138+
}
139+
140+
func (m *MockModelHandler) Delete(request handler.Request) handler.ProgressEvent {
141+
return m.fn(request)
142+
}
143+
144+
func (m *MockModelHandler) List(request handler.Request) handler.ProgressEvent {
145+
return m.fn(request)
146+
}

0 commit comments

Comments
 (0)