Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aws-lambda) Add support for setting a custom response code #2587

Merged

Conversation

erran
Copy link
Contributor

@erran erran commented May 31, 2017

This is a feature request for the Kong aws-lambda plugin. The use case is for allowing users to set a custom response code for APIs they've configured. AWS Lambda's Invoke call returns a 20x code regardless of whether an error occurred. This supports configuring a different response code via the API.

Summary

Adds a new unhandled_status configuration option for the aws-lambda
plugin. If set - when the X-Amzn-Function-Error invoke call response
header is set to Unhandled then the numeric value will be used as the
response code for the API.

Full changelog

  • Add support to override response code for unhandled exceptions via unhandled_status.
  • Updated spec for usage without unhandled_status.
  • Added specs for unhandled_status usage.

Looks like there will be conflicts with #2470 depending on which is merged first. 😅

@thibaultcha
Copy link
Member

Hi!

Thanks for the patch, this definitely looks like a good addition :) Do you have any resource on the possible values of this X-Amzn-Function-Error header? Just curious about what would be the other possible corner cases where this sort of customization might lead us.

@erran
Copy link
Contributor Author

erran commented May 31, 2017

Do you have any resource on the possible values of this X-Amzn-Function-Error header?

@thibaultcha As per the docs on the AWS Lambda Invoke call:

FunctionError
Indicates whether an error occurred while executing the Lambda function. If an error occurred this field will have one of two values; Handled or Unhandled. Handled errors are errors that are reported by the function while the Unhandled errors are those detected and reported by AWS Lambda. Unhandled errors include out of memory errors and function timeouts. For information about how to report an Handled error, see Programming Model.

@@ -83,7 +83,11 @@ function AWSLambdaHandler:access(conf)
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
end

ngx.status = res.status
if headers['X-Amzn-Function-Error'] == 'Unhandled' and conf.unhandled_response ~= -1 then
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can someone confirm the upgrade path this plugin would take? I'm imagining it'd get the default value -1 but I couldn't confirm before submitting this PR.

Copy link
Member

@thibaultcha thibaultcha May 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was about to mention this as well. When adding a new property in the plugin configuration, one needs to do one of those two things:

  • create a migration for both C* and Postgres that adds the property (alongside its default) in all the existing rows containing this plugin configuration
  • handle the possibility that said configuration property is nil in the Lua logic

We historically used to prefer the former approach, to stay consistent with previous cases where a property was added to an existing plugin, and to follow the philosophy that the configuration schema is the source of truth, and a required field is guaranteed to be present. Lately however, I've been more and more of a fan of the second approach, since we avoid adding migrations to the projects, and it allows us to ship new features into our minor releases. If you retrieve the default from the schema when conf.unhandled_response is nil, or if you handle it in a non-breaking fashion I think you should be just fine :)

Copy link
Member

@thibaultcha thibaultcha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments and concerns :)

@@ -14,5 +14,6 @@ return {
log_type = {type = "string", required = true, default = "Tail",
enum = {"Tail", "None"}},
port = { type = "number", default = 443 },
unhandled_response = {type = "number", default = -1, required = true},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure -1 is a healthy default... There might be plenty of HTTP clients being confused by the negative number here (including Nginx itself). Would something in the 5xx range be a healthier default? This is partly why I was wondering about this X-Amzn-Function-Error header and its possible values. Not so obvious to find doc about it at first sight.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a second note on that line, wouldn't unhandled_status be a more appropriate name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would something in the 5xx range be a healthier default?

I was using -1 as the value so I could short circuit if the flag was unset. What happens to numeric values when unset?

As a second note on that line, wouldn't unhandled_status be a more appropriate name?

Yep, I'll change it and was conflicted on what to use as the name myself. 👌

Copy link
Member

@thibaultcha thibaultcha May 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens to numeric values when unset?

They would just be nil. A validation function that ensure a valid HTTP status > 99 && <= 999 would also be nice. As we know, HTTP clients don't have a good time when the status is not included in that range.

I like the current approach of only setting ngx.status when unhandled_status is not nil.

@@ -83,7 +83,11 @@ function AWSLambdaHandler:access(conf)
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
end

ngx.status = res.status
if headers['X-Amzn-Function-Error'] == 'Unhandled' and conf.unhandled_response ~= -1 then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: we use double quotes in this codebase, and try to avoid having lines over 80 characters long. See our style guide: https://github.com/Mashape/kong/blob/master/CONTRIBUTING.md#code-style

ngx.status = res.status
if headers['X-Amzn-Function-Error'] == 'Unhandled' and conf.unhandled_response ~= -1 then
ngx.status = conf.unhandled_response
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: we need a line jump before this else

@@ -109,6 +185,7 @@ describe("Plugin: AWS Lambda (access)", function()
local body = assert.res_status(200, res)
assert.is_string(res.headers["x-amzn-RequestId"])
assert.equal([["some_value1"]], body)
assert.equal(nil, res.headers["X-Amzn-Function-Error"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer assert.is_nil :)

@erran
Copy link
Contributor Author

erran commented May 31, 2017

handle the possibility that said configuration property is nil in the Lua logic

I'm not sure -1 is a healthy default... There might be plenty of HTTP clients being confused by the negative number here (including Nginx itself).

Would something in the 5xx range be a healthier default?

Yea, -1 didn't play nice with nginx in my testing but it was fine as a shortcut only value. I like using nil since I we can just check that the value is truthy.

A 5xx code would be a change in behaviour since today hitting a Kong API which forwards to a lambda that encountered an Unhandled error gives a 20x status code.

Looks like I collapsed #2587 (review) already after pushing one of those style fixed but it also included the change to support unhandled_status being nil which should maintain backwards with the plugin as per the above comments.


Going to catch some 💤s — I'll be back online around 10am Dublin time. I'll squash and rebase the commits in this PR against master in the morning.

@erran erran force-pushed the feat/aws-lambda-plugin-custom-response-code branch from 99b9be1 to 0496f33 Compare June 1, 2017 08:18
@erran
Copy link
Contributor Author

erran commented Jun 1, 2017

Had one spec error in TravisCI Build #7298.5:

TravisCI Build failure
[ RUN      ] spec/02-integration/02-dao/08-ipc_events_spec.lua @ 42: Quiet with #cassandra insert() propagates event
./kong/dao/schemas/apis.lua:209: attempt to index local 't' (a nil value)
stack traceback:
	./kong/dao/schemas/apis.lua:209: in function 'marshall_event'
	./kong/dao/dao.lua:68: in function 'event'
	./kong/dao/dao.lua:134: in function 'insert'
	spec/02-integration/02-dao/08-ipc_events_spec.lua:26: in function 'do_insert'
	spec/02-integration/02-dao/08-ipc_events_spec.lua:53: in function 
[  ERROR   ] spec/02-integration/02-dao/08-ipc_events_spec.lua @ 42: Quiet with #cassandra insert() propagates event (212.09 ms)

Looks like it may be due to the the new value in the schema?

Or could be an intermittent failure unrelated to my changes. 😓 I couldn't reproduce the above failure in my kong-vagrant instance running the integration make target. After making additional changes and rebasing/pushing we're green again though the changes weren't functional. 😕

@erran erran force-pushed the feat/aws-lambda-plugin-custom-response-code branch from 0496f33 to c6ee83b Compare June 1, 2017 13:40
@p0pr0ck5
Copy link
Contributor

p0pr0ck5 commented Jun 1, 2017

@erran yep, I retriggered the Travis build and it did indeed pass :)

Copy link
Contributor

@p0pr0ck5 p0pr0ck5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Picking up for @thibaultcha while he travels for LuaConf. @erran thanks for the PR! A few minor comments attached.

@@ -14,5 +14,6 @@ return {
log_type = {type = "string", required = true, default = "Tail",
enum = {"Tail", "None"}},
port = { type = "number", default = 443 },
unhandled_status = {type = "number"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pedantic style: can we add spaces around the table elements, e.g.

{ type = "number" }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, a validation function to check for status > 99 and status <= 999 would be great here :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Awesome, sad I removed the validation in one of my earlier commits now.

I'm happy to add spaces. Just getting the hang of this lua thing. 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed both of the above in 61dc1db.

@@ -1,3 +1,7 @@
local check_status = function(status)
return status > 99 and status <= 999
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caused a failure due to not nil checking. Fixed locally and will push once specs have run.

Copy link
Contributor Author

@erran erran Jun 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@p0pr0ck5 I've pushed up the fix in 731519e and the plugin specs are passing. Looks like one of the restart Kong integration specs failed: https://travis-ci.org/Mashape/kong/jobs/238408326#L551-560

@@ -1,3 +1,14 @@
local function check_status(status)
if not status then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be unnecessary. If there is no status, the func check will simply not run.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I got a null pointer when I didn't check this. I'll confirm when I'm back at the keyboard.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm possible - it's been too long since I last touched this. If so, then I think the logic can be shortened and we can only use 1 branch, that returns false if status and status isn't in range, and true otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, see TravisCI Build #7300.4 from before adding the null check.

[----------] Running tests from spec/03-plugins/23-aws-lambda/01-access_spec.lua
./kong/plugins/aws-lambda/schema.lua:2: attempt to compare number with nil
stack traceback:
	./kong/plugins/aws-lambda/schema.lua:2: in function 'func'
	./kong/dao/schemas_validation.lua:215: in function 'validate_entity'
	./kong/dao/schemas_validation.lua:186: in function 'validate'
	./kong/dao/model_factory.lua:27: in function 'validate'
	./kong/dao/dao.lua:118: in function 'insert'
	spec/03-plugins/23-aws-lambda/01-access_spec.lua:55: in function <spec/03-plugins/23-aws-lambda/01-access_spec.lua:6>
[----------] 0 tests from spec/03-plugins/23-aws-lambda/01-access_spec.lua (736.92 ms total)

Pushed an update in 292d9a2404a.

@@ -1,3 +1,14 @@
local function check_status(status)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need a test for this validation function similar to the schema_spec tests you can found in other parts of the unit test suite.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thibaultcha lazy me, was actually thinking about it and found the hmac-auth schema validation spec earlier. 😅 Added in 292d9a2404a1a0e40104c6346f14b11cd8b73013.

@thibaultcha
Copy link
Member

Picking up for @thibaultcha while he travels for LuaConf.

Thanks @p0pr0ck5!

@erran erran force-pushed the feat/aws-lambda-plugin-custom-response-code branch from 706638f to ad0a101 Compare June 1, 2017 20:43
@erran
Copy link
Contributor Author

erran commented Jun 1, 2017

Thanks for the reviews @thibaultcha and @p0pr0ck5. Rebased against master and pushed up including all the above feedback. 👌

@thibaultcha
Copy link
Member

Thanks for taking care of the reviews! This looks good now :) Currently on mobile so I'll let @p0pr0ck5 merge this one.

port = 443,
}

it("accepts correct Unhandled Response Status Code", function()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for completeness, can we add a test for when unhandled_status is undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 2f888f9. Should I squash my commits again?

Copy link
Contributor

@p0pr0ck5 p0pr0ck5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGMT! Thanks for the contribution!

@p0pr0ck5
Copy link
Contributor

p0pr0ck5 commented Jun 1, 2017

@erran can you squash these commits, and then we can merge this in?

Adds a new `unhandled_status` configuration option for the aws-lambda
plugin. If set the response status will be overridden if the
`X-Amzn-Function-Error` lambda invoke respoonse header was set to
`Unhandled`.

This gives a new feature to users of the plugin allowing their API to
return non 20x codes. The previous functionality of responding with
Amazon's Lambda Invoke unhandled response status of 200 (RequestResponse),
202 (Event), or 204 (DryRun) remains.
@erran erran force-pushed the feat/aws-lambda-plugin-custom-response-code branch from 2f888f9 to 52779f2 Compare June 1, 2017 21:54
@erran
Copy link
Contributor Author

erran commented Jun 1, 2017

@p0pr0ck5 @thibaultcha thanks for the quick feedback and help getting my changes up to shape. 😄

Squashed in 52779f2.

@p0pr0ck5 p0pr0ck5 merged commit 1d22b16 into Kong:master Jun 2, 2017
@p0pr0ck5
Copy link
Contributor

p0pr0ck5 commented Jun 2, 2017

Merged! Thanks again @erran for the contribution!

@erran erran deleted the feat/aws-lambda-plugin-custom-response-code branch June 2, 2017 10:11
@erran
Copy link
Contributor Author

erran commented Jun 2, 2017

@p0pr0ck5 No problem, thanks for helping me get this in! 🎉

erran added a commit to erran/getkong.org that referenced this pull request Jun 3, 2017
erran added a commit to erran/getkong.org that referenced this pull request Jun 3, 2017
thibaultcha pushed a commit to Kong/docs.konghq.com that referenced this pull request Jun 6, 2017
Docs for  Kong/kong#2587

Signed-off-by: Thibault Charbonnier <thibaultcha@me.com>
thibaultcha pushed a commit to Kong/docs.konghq.com that referenced this pull request Jun 12, 2017
Docs for  Kong/kong#2587

Signed-off-by: Thibault Charbonnier <thibaultcha@me.com>
thibaultcha pushed a commit to Kong/docs.konghq.com that referenced this pull request Sep 11, 2017
Docs for Kong/kong#2587

Signed-off-by: Thibault Charbonnier <thibaultcha@me.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants