Skip to content

Commit

Permalink
Merge pull request #546 from 3scale/cache-policy
Browse files Browse the repository at this point in the history
Cache policy
  • Loading branch information
davidor authored Jan 17, 2018
2 parents 43ab175 + 2016d41 commit db74364
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 1 deletion.
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/).
- URL rewriting policy [PR #529](https://github.com/3scale/apicast/pull/529)
- Liquid template can find files in current folder too [PR #533](https://github.com/3scale/apicast/pull/533)
- `bin/apicast` respects `APICAST_OPENRESTY_BINARY` and `TEST_NGINX_BINARY` environment [PR #540](https://github.com/3scale/apicast/pull/540)
- Caching policy [PR #546](https://github.com/3scale/apicast/pull/546)

## Fixed

Expand Down
2 changes: 1 addition & 1 deletion gateway/src/apicast/backend/cache_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function _M.handlers.strict(cache, cached_key, response, ttl)
-- so to not write the cache twice lets write it just in authorize

if fetch_cached_key(cached_key) ~= cached_key then
ngx.log(ngx.INFO, 'apicast cache write key: ', cached_key, ', ttl: ', ttl, ' sub: ')
ngx.log(ngx.INFO, 'apicast cache write key: ', cached_key, ', ttl: ', ttl)
cache:set(cached_key, 200, ttl or 0)
end
else
Expand Down
5 changes: 5 additions & 0 deletions gateway/src/apicast/policy/apicast/policy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ function _M:rewrite(context)
-- because the module is reloaded and has to be configured again

local p = context.proxy

if context.cache_handler then
p.cache_handler = context.cache_handler
end

p.set_upstream(context.service)
ngx.ctx.proxy = p
end
Expand Down
80 changes: 80 additions & 0 deletions gateway/src/apicast/policy/caching/policy.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
--- Caching policy
-- Configures a cache for the authentication calls against the 3scale backend.
-- The 3scale backend can authorize (status code = 200) and deny (status code =
-- 4xx) calls. When it fails, it returns a 5xx code.
-- This policy support three kinds of caching:
-- - Strict: it only caches authorized calls. Denied and failed calls
-- invalidate the cache entry.
-- - Resilient: caches authorized and denied calls. Failed calls do not
-- invalidate the cache. This allows us to authorize and deny calls
-- according to the result of the last request made even when backend is
-- down.
-- - None: disables caching.

local policy = require('apicast.policy')
local _M = policy.new('Caching policy')

local new = _M.new

local function strict_handler(cache, cached_key, response, ttl)
if response.status == 200 then
ngx.log(ngx.INFO, 'apicast cache write key: ', cached_key, ', ttl: ', ttl)
cache:set(cached_key, 200, ttl or 0)
else
ngx.log(ngx.NOTICE, 'apicast cache delete key: ', cached_key,
' cause status ', response.status)
cache:delete(cached_key)
end
end

local function resilient_handler(cache, cached_key, response, ttl)
local status = response.status

if status and status < 500 then
ngx.log(ngx.INFO, 'apicast cache write key: ', cached_key,
' status: ', status, ', ttl: ', ttl)

cache:set(cached_key, status, ttl or 0)
end
end

local function disabled_cache_handler()
ngx.log(ngx.DEBUG, 'Caching is disabled. Skipping cache handler.')
end

local handlers = {
resilient = resilient_handler,
strict = strict_handler,
none = disabled_cache_handler
}

local function handler(config)
if not config.caching_type then
ngx.log(ngx.ERR, 'Caching type not specified. Disabling cache.')
return handlers.none
end

local res = handlers[config.caching_type]

if not res then
ngx.log(ngx.ERR, 'Invalid caching type. Disabling cache.')
res = handlers.none
end

return res
end

--- Initialize a Caching policy.
-- @tparam[opt] table config
-- @field caching_type Caching type (strict, resilient)
function _M.new(config)
local self = new()
self.cache_handler = handler(config or {})
return self
end

function _M:rewrite(context)
context.cache_handler = self.cache_handler
end

return _M
11 changes: 11 additions & 0 deletions gateway/src/apicast/policy/caching/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Caching policy configuration",
"type": "object",
"properties": {
"exit": {
"type": "caching_type",
"enum": ["resilient", "strict", "none"]
}
}
}
111 changes: 111 additions & 0 deletions spec/policy/caching/policy_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
local resty_lrucache = require('resty.lrucache')

describe('policy', function()
describe('.new', function()
local cache = resty_lrucache.new(1)

it('disables caching when caching type is not specified', function()
local caching_policy = require('apicast.policy.caching').new({})
local ctx = {}
caching_policy:rewrite(ctx)

ctx.cache_handler(cache, 'a_key', { status = 200 }, nil)
assert.is_nil(cache:get('a_key'))
end)

it('disables caching when invalid caching type is specified', function()
local config = { caching_type = 'invalid_caching_type' }
local caching_policy = require('apicast.policy.caching').new(config)
local ctx = {}
caching_policy:rewrite(ctx)

ctx.cache_handler(cache, 'a_key', { status = 200 }, nil)
assert.is_nil(cache:get('a_key'))
end)
end)

describe('.access', function()
describe('when configured as strict', function()
local caching_policy
local cache
local ctx -- the caching policy will add the handler here

before_each(function()
local config = { caching_type = 'strict' }
caching_policy = require('apicast.policy.caching').new(config)
ctx = { }
caching_policy:rewrite(ctx)
cache = resty_lrucache.new(1)
end)

it('caches authorized requests', function()
ctx.cache_handler(cache, 'a_key', { status = 200 }, nil)
assert.equals(200, cache:get('a_key'))
end)

it('clears the cache entry for a request when it is denied', function()
cache:set('a_key', 200)

ctx.cache_handler(cache, 'a_key', { status = 403 }, nil)
assert.is_nil(cache:get('a_key'))
end)

it('clears the cache entry for a request when it fails', function()
cache:set('a_key', 200)

ctx.cache_handler(cache, 'a_key', { status = 500 }, nil)
assert.is_nil(cache:get('a_key'))
end)
end)

describe('when configured as resilient', function()
local caching_policy
local cache
local ctx -- the caching policy will add the handler here

before_each(function()
local config = { caching_type = 'resilient' }
caching_policy = require('apicast.policy.caching').new(config)
ctx = { }
caching_policy:rewrite(ctx)
cache = resty_lrucache.new(1)
end)

it('caches authorized requests', function()
ctx.cache_handler(cache, 'a_key', { status = 200 }, nil)
assert.equals(200, cache:get('a_key'))
end)

it('caches denied requests', function()
ctx.cache_handler(cache, 'a_key', { status = 403 }, nil)
assert.equals(403, cache:get('a_key'))
end)

it('does not clear the cache entry for a request when it fails', function()
cache:set('a_key', 200)

ctx.cache_handler(cache, 'a_key', { status = 500 }, nil)
assert.equals(200, cache:get('a_key'))
end)
end)

describe('when disabled', function()
local caching_policy
local cache
local ctx

setup(function()
local config = { caching_type = 'none' }
caching_policy = require('apicast.policy.caching').new(config)
ctx = {}
caching_policy:rewrite(ctx)
cache = resty_lrucache.new(1)
end)

it('does not cache anything', function()
ctx.cache_handler(cache, 'a_key', { status = 200 }, nil)
assert.is_nil(cache:get('a_key'))
end)
end)
end)
end)
Loading

0 comments on commit db74364

Please sign in to comment.