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

[THREESCALE-3189] Add maintenance policy #1105

Merged
merged 7 commits into from
Aug 7, 2019
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added new parameters to logging policy to allow custom access log [PR #1081](https://github.com/3scale/APIcast/pull/1089) [THREESCALE-1234](https://issues.jboss.org/browse/THREESCALE-1234)[THREESCALE-2876](https://issues.jboss.org/browse/THREESCALE-2876)
- Added http_proxy policy to use an HTTP proxy in api_backed calls. [THREESCALE-2696](https://issues.jboss.org/browse/THREESCALE-2696) [PR #1080](https://github.com/3scale/APIcast/pull/1080)
- Option to load service configurations one by one lazily [PR #1099](https://github.com/3scale/APIcast/pull/1099)
- New maintenance mode policy, useful for maintenance periods. [PR #1105](https://github.com/3scale/APIcast/pull/1105), [THREESCALE-3189](https://issues.jboss.org/browse/THREESCALE-3189)

## [3.6.0-rc1] - 2019-07-04

Expand Down
22 changes: 22 additions & 0 deletions gateway/src/apicast/policy/maintenance_mode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Maintenance Mode

A policy which allows you reject incoming requests with a specified status code
and message. It's useful for maintenance periods or to temporarily block an API.

## Properties

| Property | Default | Description |
|------------------------------|-----------------------------------|------------------|
| status (integer, _optional_) | 503 | Response code |
| message (string, _optional_) | Service Unavailable - Maintenance | Response message |

## Example Configuration
```json
{
"name": "maintenance-mode",
"configuration": {
"message": "Be back soon..",
"status": 503
}
}
```
29 changes: 29 additions & 0 deletions gateway/src/apicast/policy/maintenance_mode/apicast-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "http://apicast.io/policy-v1.1/schema#manifest#",
"name": "Maintenance Mode",
"summary": "Rejects incoming requests. Useful for maintenance periods.",
"description": ["A policy which allows you reject incoming requests with a specified status code and message.",
"It's useful for maintenance periods or to temporarily block an API."
],
"version": "builtin",
"configuration": {
"type": "object",
"properties": {
"status": {
"type": "integer",
"description": "HTTP status code to return",
"default": 503
},
"message": {
"type": "string",
"description": "HTTP response to return",
"default": "Service Unavailable - Maintenance"
},
"message_content_type": {
"type": "string",
"description": "Content-Type header for the response",
"default": "text/plain; charset=utf-8"
}
}
}
}
1 change: 1 addition & 0 deletions gateway/src/apicast/policy/maintenance_mode/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require('maintenance_mode')
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- This is a simple policy. It allows you reject incoming requests with a
-- specified status code and message.
-- It's useful for maintenance periods or to temporarily block an API

local _M = require('apicast.policy').new('Maintenance mode', 'builtin')

local tonumber = tonumber
local new = _M.new

local default_status_code = 503
local default_message = "Service Unavailable - Maintenance"
local default_message_content_type = "text/plain; charset=utf-8"

function _M.new(configuration)
local policy = new(configuration)

policy.status_code = default_status_code
policy.message = default_message
policy.message_content_type = default_message_content_type

if configuration then
policy.status_code = tonumber(configuration.status) or policy.status_code
policy.message = configuration.message or policy.message
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be useful to also set some header in case this is active. So you can have the payload as JSON for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what you mean by setting a header here.
Do you mean returning JSON or plain text according to the value of the Accept header of the request?

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean setting also Content-Type header when the policy is active. JSON payload is incorrect without proper Content-Type. And using two policies to activate mantenance mode would be weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

bcee0a9

policy.message_content_type = configuration.message_content_type or policy.message_content_type
end

return policy
end

function _M:rewrite()
ngx.header['Content-Type'] = self.message_content_type
ngx.status = self.status_code
ngx.say(self.message)

return ngx.exit(ngx.status)
eloycoto marked this conversation as resolved.
Show resolved Hide resolved
end

return _M
76 changes: 76 additions & 0 deletions spec/policy/maintenance_mode/maintenance_mode_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
local MaintenancePolicy = require('apicast.policy.maintenance_mode')

describe('Maintenance mode policy', function()
describe('.rewrite', function()
before_each(function()
stub(ngx, 'say')
stub(ngx, 'exit')
ngx.header = {}
end)

context('when using the defaults', function()
local maintenance_policy = MaintenancePolicy.new()

it('returns 503', function()
maintenance_policy:rewrite()

assert.stub(ngx.exit).was_called_with(503)
end)

it('returns the default message', function()
maintenance_policy:rewrite()

assert.stub(ngx.say).was_called_with('Service Unavailable - Maintenance')
end)

it('returns the default Content-Type header', function()
maintenance_policy:rewrite()

assert.equals('text/plain; charset=utf-8', ngx.header['Content-Type'])
end)
end)

context('when using a custom status code', function()
it('returns that status code', function()
local custom_code = 555
local maintenance_policy = MaintenancePolicy.new(
{ status = custom_code }
)

maintenance_policy:rewrite()

assert.stub(ngx.exit).was_called_with(custom_code)
end)
end)

context('when using a custom message', function()
it('returns that message', function()
local custom_msg = 'Some custom message'
local maintenance_policy = MaintenancePolicy.new(
{ message = custom_msg }
)

maintenance_policy:rewrite()

assert.stub(ngx.say).was_called_with(custom_msg)
end)
end)

context('when using a custom content type', function()
it('sets the Content-Type header accordingly', function()
local custom_content_type = 'application/json'
local maintenance_policy = MaintenancePolicy.new(
{
message = '{ "msg": "some_msg" }',
message_content_type = custom_content_type
}
)


maintenance_policy:rewrite()

assert.equals('application/json', ngx.header['Content-Type'])
end)
end)
end)
end)
183 changes: 183 additions & 0 deletions t/apicast-policy-maintenance-mode.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use lib 't';
use Test::APIcast::Blackbox 'no_plan';

run_tests();

__DATA__

=== TEST 1: Use maintenance mode using default values
Testing 3 things:
1) Check default status code
2) Check default response message
3) Validates upstream doesn't get the request

--- configuration
{
"services": [
{
"id": 42,
"backend_version": 1,
"backend_authentication_type": "service_token",
"backend_authentication_value": "token-value",
"proxy": {
"policy_chain": [
{ "name": "apicast.policy.maintenance_mode" },
{ "name": "apicast.policy.apicast" }
],
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/",
"proxy_rules": [
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 }
]
}
}
]
}
--- upstream
location / {
content_by_lua_block {
local assert = require('luassert')
assert.is_true(false)
}
}
--- request
GET /
--- response_body
Service Unavailable - Maintenance
--- error_code: 503
--- no_error_log
[error]


=== TEST 2: Use maintenance mode using custom values
Testing 3 things:
1) Check custom status code
2) Check custom response message
3) Validates upstream doesn't get request

--- configuration
{
"services": [{
"id": 42,
"backend_version": 1,
"backend_authentication_type": "service_token",
"backend_authentication_value": "token-value",
"proxy": {
"policy_chain": [{
"name": "apicast.policy.maintenance_mode",
"configuration": {
"message": "Be back soon",
"status": 501
}
},
{
"name": "apicast.policy.apicast"
}
],
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/",
"proxy_rules": [{
"pattern": "/",
"http_method": "GET",
"metric_system_name": "hits",
"delta": 2
}]
}
}]
}
--- upstream
location / {
content_by_lua_block {
local assert = require('luassert')
assert.is_true(false)
}
}
--- request
GET /
--- response_body
Be back soon
--- error_code: 501
--- no_error_log
[error]

=== TEST 3: Maintenance policy works when placed after the APIcast policy
In this test we need to send the app credentials, because APIcast will check
that they are there before the maintenance policy runs.
--- configuration
{
"services": [
{
"id": 42,
"backend_version": 1,
"backend_authentication_type": "service_token",
"backend_authentication_value": "token-value",
"proxy": {
"policy_chain": [
{ "name": "apicast.policy.apicast" },
{ "name": "apicast.policy.maintenance_mode" }
],
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/",
"proxy_rules": [
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 }
]
}
}
]
}
--- upstream
location / {
content_by_lua_block {
local assert = require('luassert')
assert.is_true(false)
}
}
--- request
GET /?user_key=uk
--- response_body
Service Unavailable - Maintenance
--- error_code: 503
--- no_error_log
[error]

=== TEST 4: custom content-type
--- configuration
{
"services": [
{
"id": 42,
"backend_version": 1,
"backend_authentication_type": "service_token",
"backend_authentication_value": "token-value",
"proxy": {
"policy_chain": [
{
"name": "apicast.policy.maintenance_mode",
"configuration": {
"message": "{ \"msg\": \"Be back soon\" }",
"message_content_type": "application/json"
}
},
{ "name": "apicast.policy.apicast" }
],
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/",
"proxy_rules": [
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 }
]
}
}
]
}
--- upstream
location / {
content_by_lua_block {
local assert = require('luassert')
assert.is_true(false)
}
}
--- request
GET /
--- response_body
{ "msg": "Be back soon" }
--- response_headers
Content-Type: application/json
--- error_code: 503
--- no_error_log
[error]